// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml.Serialization;

using Microsoft.TeamFoundation.Migration.Toolkit;
using Microsoft.TeamFoundation.Migration.Toolkit.Wit;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Linking
{
    /// <summary>
    /// Abstract link engine class.
    /// </summary>
    public abstract class LinkEngineBase : ILinkEngine
    {
        #region SqlData class
        /// <summary>
        /// SQL constants.
        /// </summary>
        static class SqlData
        {
            public const int UriSize = 400;                 // Max URI size
            public const int TypeSize = 400;                // Max type size
        }

        #endregion

        private WorkItemTrackingSession m_witSession;       // Work item tracking session
        private VersionControlSession m_vcSession;          // Version control session
        private ExtractLinks m_getLinks;                  // Delegate for getting links
        private SystemPair<Collection<IArtifactHandler>> m_handlers;    // Artifact handlers

        /// <summary>
        /// Delegate function for extracting links from the given object.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Collection of links to populate</param>
        public delegate void ExtractLinks(ILinkContainer source, ICollection<ILink> links);

        /// <summary>
        /// Returns artifact handlers for TFS system.
        /// </summary>
        public Collection<IArtifactHandler> TfsHandlers { get { return m_handlers.Tfs; } }

        /// <summary>
        /// Returns artifact handlers for other system.
        /// </summary>
        public Collection<IArtifactHandler> OtherHandlers { get { return m_handlers.Other; } }

        /// <summary>
        /// Gets callback method for extracting links.
        /// </summary>
        public ExtractLinks ExtractLinksCallback { get { return m_getLinks; } set { m_getLinks = value; } }


        /// <summary>
        /// Constructor.
        /// </summary>
        protected LinkEngineBase()
        {
            m_handlers = new SystemPair<Collection<IArtifactHandler>>(
                new Collection<IArtifactHandler>(),
                new Collection<IArtifactHandler>());
        }

        #region ILinkEngine Members

        /// <summary>
        /// Work item tracking session.
        /// </summary>
        public WorkItemTrackingSession WorkItemTrackingSession 
        { 
            get { return m_witSession; } 
            set { m_witSession = value; } 
        }

        /// <summary>
        /// Version control session.
        /// </summary>
        public VersionControlSession VersionControlSession
        {
            get { return m_vcSession; }
            set { m_vcSession = value; }
        }

        /// <summary>
        /// Obtains all supported links from the given source.
        /// </summary>
        /// <returns>List of supported links</returns>
        public ReadOnlyCollection<ILink> GetLinks(
            ILinkContainer source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            List<ILink> links = new List<ILink>();
            m_getLinks(source, links);
            return links.AsReadOnly();
        }

        /// <summary>
        /// Defers the link.
        /// </summary>
        /// <param name="system">System the link belong to</param>
        /// <param name="link">Link to defer</param>
        public void Defer(
            SystemType system,
            ILink link)
        {
            if (link == null)
            {
                throw new ArgumentNullException("link");
            }

            if (m_witSession == null)
            {
                throw new MigrationException(MigrationToolkitResources.ErrorNoWitSessionInLinking);
            }

            string linkData = link.Serialize();

            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    cmd.CommandText = "prc_iiDeferLink";
                    cmd.CommandType = CommandType.StoredProcedure;

                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_witSession.InternalId;
                    cmd.Parameters.Add("@IsTfs", SqlDbType.Bit).Value = system == SystemType.Tfs;
                    cmd.Parameters.Add("@Source", SqlDbType.NVarChar, SqlData.UriSize).Value = link.SourceUri;
                    cmd.Parameters.Add("@Target", SqlDbType.NVarChar, SqlData.UriSize).Value = link.TargetUri;
                    cmd.Parameters.Add("@LinkType", SqlDbType.NVarChar, SqlData.TypeSize).Value = link.LinkType;
                    cmd.Parameters.Add("@LinkData", SqlDbType.NVarChar).Value = linkData;

                    cmd.ExecuteNonQuery();
                }
                trn.Complete();
            }

            if (LinkDeferred != null)
            {
                LinkingEventArgs args = new LinkingEventArgs(link);
                LinkDeferred(this, args);
            }
        }

        /// <summary>
        /// Removes deferred link.
        /// </summary>
        /// <param name="system">System the link belongs to</param>
        /// <param name="link">Link to remove</param>
        public void RemoveDeferred(
            SystemType system,
            ILink link)
        {
            if (link == null)
            {
                throw new ArgumentNullException("link");
            }

            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.CommandText = "prc_iiRemoveDeferredLink";
                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_witSession.InternalId;
                    cmd.Parameters.Add("@IsTfs", SqlDbType.Bit).Value = system == SystemType.Tfs;
                    cmd.Parameters.Add("@LinkType", SqlDbType.NVarChar, SqlData.TypeSize).Value = link.LinkType;
                    cmd.Parameters.Add("@Source", SqlDbType.NVarChar, SqlData.UriSize).Value = link.SourceUri;
                    cmd.Parameters.Add("@Target", SqlDbType.NVarChar, SqlData.UriSize).Value = link.TargetUri;

                    cmd.ExecuteNonQuery();
                }
                trn.Complete();
            }
        }

        /// <summary>
        /// Gets all deferred links originating from the given source.
        /// </summary>
        /// <param name="system">System the source artifact belongs to</param>
        /// <param name="sourceUri">URI of the source artifact</param>
        /// <returns>List of links</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public ReadOnlyCollection<ILink> GetDeferredLinks(
            SystemType system,
            string sourceUri)
        {
            if (string.IsNullOrEmpty(sourceUri))
            {
                throw new ArgumentNullException("sourceUri");
            }

            if (m_witSession == null)
            {
                throw new MigrationException(MigrationToolkitResources.ErrorNoWitSessionInLinking);
            }

            List<ILink> links = new List<ILink>();

            using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.CommandText = "prc_iiGetDeferredLinks";

                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_witSession.InternalId;
                    cmd.Parameters.Add("@Uri", SqlDbType.NVarChar, SqlData.UriSize).Value = sourceUri;
                    cmd.Parameters.Add("@IsTfs", SqlDbType.Bit).Value = system == SystemType.Tfs;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            try
                            {
                                Type t = Type.GetType((string)reader[1]);
                                XmlSerializer s = new XmlSerializer(t);

                                using (StringReader sr = new StringReader((string)reader[2]))
                                {
                                    ILink l = (ILink)s.Deserialize(sr);
                                    l.SourceUri = sourceUri;
                                    l.TargetUri = (string)reader[0];
                                    links.Add(l);
                                }
                            }
                            catch (Exception e)
                            {
                                ReportException(e);
                            }
                        }
                    }
                }
            }

            return links.AsReadOnly();
        }

        /// <summary>
        /// Enumerates all deferred links' sources.
        /// </summary>
        /// <param name="callback">Callback function</param>
        public void EnumerateDeferredLinkSources(
            EnumerateSourcesCallback callback)
        {
            if (callback == null)
            {
                throw new ArgumentNullException("callback");
            }

            using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
            {
                conn.Open();

                using (SqlCommand cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.CommandText = "prc_iiGetDeferredLinkSources";

                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = WorkItemTrackingSession.InternalId;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            callback((bool)reader[0]?SystemType.Tfs : SystemType.Other, (string)reader[1]);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Finds reflection of the given artifact.
        /// </summary>
        /// <param name="sourceSystem">System of the artifact</param>
        /// <param name="sourceUri">URI of the artifact</param>
        /// <returns>URI of the reflection, if any</returns>
        public string FindReflection(
            SystemType sourceSystem,
            string sourceUri)
        {
            if (string.IsNullOrEmpty(sourceUri))
            {
                throw new ArgumentNullException("sourceUri");
            }
            SystemType otherSystem = sourceSystem == SystemType.Tfs ? SystemType.Other : SystemType.Tfs;

            for (int i = 0; i < m_handlers[sourceSystem].Count; i++)
            {
                string targetId;

                if (m_handlers[sourceSystem][i].TryFindReflection(sourceSystem, sourceUri, out targetId))
                {
                    if (string.IsNullOrEmpty(targetId))
                    {
                        return null;
                    }
                    return m_handlers[otherSystem][i].CreateUri(targetId);
                }
            }

            Debug.Fail("Artifact handler not found!");
            TraceManager.TraceError("Artifact handler not found for '{0}'", sourceUri);
            return null;
        }

        /// <summary>
        /// Translates URI and extracts tool-specific id.
        /// </summary>
        /// <param name="sourceUri">Source URI</param>
        /// <param name="id">Tool id</param>
        /// <returns>Tool-specific id, if any</returns>
        public bool TranslateUri(
            string sourceUri,
            out string id)
        {
            id = null;
            if (string.IsNullOrEmpty(sourceUri))
            {
                throw new ArgumentNullException("sourceUri");
            }

            for (int i = 0; i < m_handlers.Tfs.Count; i++)
            {
                IArtifactHandler left = m_handlers.Tfs[i];
                IArtifactHandler right = m_handlers.Other[i];

                if (left.TryTranslateUri(sourceUri, out id))
                {
                    return true;
                }
                if (!object.ReferenceEquals(left.GetType(), right.GetType()))
                {
                    if (right.TryTranslateUri(sourceUri, out id))
                    {
                        return true;
                    }
                }
            }
            Debug.Fail("Artifact handler not found!");
            TraceManager.TraceError("Artifact handler not found for '{0}'", sourceUri);
            return false;
        }

        // Linking events
        public event EventHandler<LinkingEventArgs> LinkDeferred;
        public event EventHandler<MigrationEventArgs> EngineFailure;
        #endregion

        #region ExtractLinks delegates

        /// <summary>
        /// Obtains hyperlinks from a work item.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Target list of links</param>
        public static void GetWorkItemHyperlinkLinks(
            ILinkContainer source,
            ICollection<ILink> links)
        {
            TfsMigrationWorkItem item = source as TfsMigrationWorkItem;

            if (item != null)
            {
                foreach (Link l in item.WorkItem.Links)
                {
                    Hyperlink hl = l as Hyperlink;
                    if (hl != null)
                    {
                        links.Add(new WorkItemHyperlinkLink(source.Uri, hl));
                    }
                }
            }
        }

        /// <summary>
        /// Obtains related links from a work item.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Target list of links</param>
        public static void GetWorkItemRelatedLinks(
            ILinkContainer source,
            ICollection<ILink> links)
        {
            TfsMigrationWorkItem item = source as TfsMigrationWorkItem;

            if (item != null)
            {
                foreach (Link l in item.WorkItem.Links)
                {
                    RelatedLink rl = l as RelatedLink;

                    if (rl != null)
                    {
                        links.Add(new WorkItemRelatedLink(source.Uri, rl));
                    }
                }
            }
        }

        /// <summary>
        /// Obtains change list links from a work item.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Target list of links</param>
        public static void GetWorkItemChangeListLinks(
            ILinkContainer source,
            ICollection<ILink> links)
        {
            TfsMigrationWorkItem item = source as TfsMigrationWorkItem;

            if (item != null)
            {
                foreach (Link l in item.WorkItem.Links)
                {
                    ExternalLink el = l as ExternalLink;

                    if (el != null && WorkItemChangeListLink.IsMyLink(el))
                    {
                        links.Add(new WorkItemChangeListLink(source.Uri, el.LinkedArtifactUri, el.Comment));
                    }
                }
            }
        }

        /// <summary>
        /// Obtains latest revision links from a work item.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Target list of links</param>
        public static void GetWorkItemLatestFileLinks(
            ILinkContainer source,
            ICollection<ILink> links)
        {
            TfsMigrationWorkItem item = source as TfsMigrationWorkItem;

            if (item != null)
            {
                foreach (Link l in item.WorkItem.Links)
                {
                    ExternalLink el = l as ExternalLink;
                    if (el != null && WorkItemLatestFileLink.IsMyLink(el))
                    {
                        links.Add(new WorkItemLatestFileLink(source.Uri, el.LinkedArtifactUri, el.Comment));
                    }
                }
            }
        }

        /// <summary>
        /// Obtains specific revision links from a work item.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Target list of links</param>
        public static void GetWorkItemRevisionFileLinks(
            ILinkContainer source,
            ICollection<ILink> links)
        {
            TfsMigrationWorkItem item = source as TfsMigrationWorkItem;

            if (item != null)
            {
                foreach (Link l in item.WorkItem.Links)
                {
                    ExternalLink el = l as ExternalLink;
                    if (el != null && WorkItemRevisionFileLink.IsMyLink(el))
                    {
                        links.Add(new WorkItemRevisionFileLink(source.Uri, el.LinkedArtifactUri, el.Comment));
                    }
                }
            }
        }

        /// <summary>
        /// Obtains generic external links from a work item.
        /// </summary>
        /// <param name="source">Source of links</param>
        /// <param name="links">Target list of links</param>
        public static void GetWorkItemExternalLinks(
            ILinkContainer source,
            ICollection<ILink> links)
        {
            TfsMigrationWorkItem item = source as TfsMigrationWorkItem;

            if (item != null)
            {
                foreach (Link l in item.WorkItem.Links)
                {
                    ExternalLink el = l as ExternalLink;

                    if (el != null && WorkItemExternalLink.IsMyLink(el))
                    {
                        links.Add(new WorkItemExternalLink(
                            el.ArtifactLinkType.Name,
                            source.Uri,
                            LinkingConstants.ExternalArtifactPrefix + el.LinkedArtifactUri,
                            el.Comment));
                    }
                }
            }
        }

        #endregion

        /// <summary>
        /// Reports an exception.
        /// </summary>
        /// <param name="e">Linking exception</param>
        private void ReportException(
            Exception e)
        {
            if (EngineFailure != null)
            {
                MigrationEventArgs args = new MigrationEventArgs(Resources.ErrorLinkingEngineFailure, e);
                EngineFailure(this, args);
            }
        }

    }
}
